package cn.lili.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Map;

/**
 * HttpClientUtils
 *
 * @author Bulbasaur
 * @since 2021/7/9 1:40 上午
 */
@Slf4j
public class HttpClientUtils {

	/**
	 * org.apache.http.impl.client.CloseableHttpClient
	 */
	private static CloseableHttpClient httpClient = null;

	// 这里就直接默认固定了,因为以下三个参数在新建的method中仍然可以重新配置并被覆盖.
	/**
	 * ms毫秒,从池中获取链接超时时间
	 */
	static final int CONNECTION_REQUEST_TIMEOUT = 30000;
	/**
	 * ms毫秒,建立链接超时时间
	 */
	static final int CONNECT_TIMEOUT = 60000;
	/**
	 * ms毫秒,读取超时时间
	 */
	static final int SOCKET_TIMEOUT = 60000;

	/**
	 * 总配置,主要涉及是以下两个参数,如果要作调整没有用到properties会比较后麻烦,但鉴于一经粘贴,随处可用的特点,就不再做依赖性配置化处理了.
	 * 而且这个参数同一家公司基本不会变动. 最大总并发,很重要的参数
	 */
	static final int MAX_TOTAL = 500;
	/**
	 * 每路并发,很重要的参数
	 */
	static final int MAX_PER_ROUTE = 100;

	/**
	 * 正常情况这里应该配成MAP或LIST
	 * 细化配置参数,用来对每路参数做精细化处理,可以管控各ip的流量,比如默认配置请求baidu:80端口最大100个并发链接,
	 * 每个细化配置之ip(不重要,在特殊场景很有用)
	 */
	static final String DETAIL_HOST_NAME = "http://www.baidu.com";

	/**
	 * 每个细化配置之port(不重要,在特殊场景很有用)
	 */
	static final int DETAIL_PORT = 80;
	/**
	 * 每个细化配置之最大并发数(不重要,在特殊场景很有用)
	 */
	static final int DETAIL_MAX_PER_ROUTE = 100;

	private synchronized static CloseableHttpClient getHttpClient() {
		if (null == httpClient) {
			httpClient = init();
		}
		return httpClient;
	}

	/**
	 * 链接池初始化 这里最重要的一点理解就是. 让CloseableHttpClient 一直活在池的世界里, 但是HttpPost却一直用完就消掉.
	 * 这样可以让链接一直保持着.
	 */
	private static CloseableHttpClient init() {
		CloseableHttpClient newHotpoint;

		// 设置连接池
		ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
		LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
		Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("http", plainsf).register("https", sslsf).build();
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
		// 将最大连接数增加
		cm.setMaxTotal(MAX_TOTAL);
		// 将每个路由基础的连接增加
		cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);

		// 细化配置开始,其实这里用Map或List的for循环来配置每个链接,在特殊场景很有用.
		// 将每个路由基础的连接做特殊化配置,一般用不着
		HttpHost httpHost = new HttpHost(DETAIL_HOST_NAME, DETAIL_PORT);
		// 将目标主机的最大连接数增加
		cm.setMaxPerRoute(new HttpRoute(httpHost), DETAIL_MAX_PER_ROUTE);
		// 细化配置结束

		// 请求重试处理
		HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
			if (executionCount >= 2) {// 如果已经重试了2次，就放弃
				return false;
			}
			if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
				return true;
			}
			if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
				return false;
			}
			if (exception instanceof InterruptedIOException) {// 超时
				return false;
			}
			if (exception instanceof UnknownHostException) {// 目标服务器不可达
				return false;
			}
			if (exception instanceof SSLException) {// SSL握手异常
				return false;
			}

			HttpClientContext clientContext = HttpClientContext.adapt(context);
			HttpRequest request = clientContext.getRequest();
			// 如果请求是幂等的，就再次尝试
			return !(request instanceof HttpEntityEnclosingRequest);
		};

		// 配置请求的超时设置
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
				.setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
		newHotpoint = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig)
				.setRetryHandler(httpRequestRetryHandler).build();
		return newHotpoint;
	}

	public static String doGet(String url, Map<String, String> param) {

		// httpClient
		CloseableHttpClient httpClient = getHttpClient();

		String resultString = "";
		CloseableHttpResponse response = null;
		try {
			// 创建uri
			URIBuilder builder = new URIBuilder(url);
			if (param != null) {
				for (String key : param.keySet()) {
					builder.addParameter(key, param.get(key));
				}
			}
			URI uri = builder.build();

			// 创建http GET请求
			HttpGet httpGet = new HttpGet(uri);

			// 执行请求
			response = httpClient.execute(httpGet);
			// 判断返回状态是否为200
			if (response.getStatusLine().getStatusCode() == 200) {
				resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
			}
		} catch (Exception e) {
			log.error("get请求错误", e);
		} finally {
			try {
				if (response != null) {
					response.close();
				}
				httpClient.close();
			} catch (IOException e) {
				log.error("Get错误", e);
			}
		}
		return resultString;
	}
}